import Vue, { version, reactive, watchEffect } from "vue";
import * as defaultCompiler from "@vue/compiler-sfc";
import { compileFile } from "./transform";
import { utoa, atou } from "./utils";
import {
  SFCScriptCompileOptions,
  SFCStyleCompileOptions,
  SFCTemplateCompileOptions,
} from "@vue/compiler-sfc";
import { OutputModes } from "./output/types";

const defaultMainFile = "App.vue";

// const welcomeCode = `
// <script setup>
// import { ref } from 'vue'

// const msg = ref('Hello World!')
// </script>

// <template>
//   <h1>{{ msg }}</h1>
//   <input v-model="msg">
// </template>
// `.trim();

const welcomeCode = `
<template>
  <h1>{{ msg }}</h1>
  <input v-model="msg">
</template>
<script>
export default {
  data() {
    return { 
      msg: 'Hello World!', 
    };
  }, 
};
</script>  
`.trim();

export class File {
  // filename is the key in the files object in the store state and is used to identify the file
  // 文件名是文件对象在存储状态中的键，用于标识文件
  filename: string;
  // code is the actual content of the file zh:代码是文件的实际内容
  code: string;
  // hidden files are not shown in the file tree and are not compiled zh:隐藏的文件不会显示在文件树中，也不会被编译
  hidden: boolean;
  // compiled is the result of compiling the file zh:编译是编译文件的结果
  compiled = {
    js: "",
    css: "",
    ssr: "",
  };
  // 构造函数
  constructor(filename: string, code = "", hidden = false) {
    this.filename = filename;
    this.code = code;
    this.hidden = hidden;
  }
}
// the state of the store
export interface StoreState {
  // mainFile is the file that is rendered in the preview iframe
  //主文件是在预览iframe中呈现的文件
  mainFile: string;
  // files is a map of filename to file
  // 文件是文件名到文件的映射
  files: Record<string, File>;
  // activeFile is the file that is currently being edited
  // activeFile是当前正在编辑的文件
  activeFile: File;
  // errors is an array of errors that occurred during compilation
  //错误是编译期间发生的错误数组
  errors: (string | Error)[];
  // vueRuntimeURL is the URL of the runtime to use in the preview iframe
  // 在预览iframe中使用的运行时的URL
  vueRuntimeURL: string;
  // vueServerRendererURL is the URL of the server renderer to use in the preview iframe
  // vueServerRendererURL是在预览iframe中使用的服务器渲染器的URL
  vueServerRendererURL: string;
  // used to force reset the sandbox  zh:用于强制重置沙箱
  resetFlip: boolean;
}

export interface SFCOptions {
  script?: Omit<SFCScriptCompileOptions, "id">;
  style?: SFCStyleCompileOptions;
  template?: SFCTemplateCompileOptions;
}

export interface Store {
  state: StoreState;
  options?: SFCOptions;
  compiler: typeof defaultCompiler;
  vueVersion?: string;
  init: () => void;
  setActive: (filename: string) => void;
  addFile: (filename: string | File) => void;
  deleteFile: (filename: string) => void;
  getImportMap: () => any;
  initialShowOutput: boolean;
  initialOutputMode: OutputModes;
}

// ReplStore 构造函数参数
export interface StoreOptions {
  // serializedState is the serialized state of the store zh:序列化的状态是存储的序列化状态
  serializedState?: string;
  // showOutput is whether the output should be shown by default zh:showOutput是默认情况下是否应该显示输出
  showOutput?: boolean;
  // loose type to allow getting from the URL without inducing a typing error zh:松散类型，允许从URL获取而不引起类型错误
  outputMode?: OutputModes | string;
  // defaultVueRuntimeURL is the default URL of the runtime to use in the preview iframe zh:defaultVueRuntimeURL是在预览iframe中使用的运行时的默认URL
  defaultVueRuntimeURL?: string;
  //  defaultVueServerRendererURL is the default URL of the server renderer to use in the preview iframe zh:defaultVueServerRendererURL是在预览iframe中使用的服务器渲染器的默认URL
  defaultVueServerRendererURL?: string;
}

// ReplStore is the main store for the REPL
export class ReplStore implements Store {
  state: StoreState;
  compiler = defaultCompiler;
  vueVersion?: string;
  options?: SFCOptions;
  initialShowOutput: boolean;
  initialOutputMode: OutputModes;

  private defaultVueRuntimeURL: string;
  private defaultVueServerRendererURL: string;
  private pendingCompiler: Promise<any> | null = null;

  // `https://unpkg.com/browse/vue@${version}/dist/vue.esm.browser.js`,
  constructor({
    serializedState = "",
    defaultVueRuntimeURL = `https://unpkg.com/@vue/runtime-dom@3.2.47/dist/runtime-dom.esm-browser.js`,
    defaultVueServerRendererURL = `https://unpkg.com/@vue/server-renderer@3.2.47/dist/server-renderer.esm-browser.js`,
    showOutput = false,
    outputMode = "preview",
  }: StoreOptions = {}) {
    console.log(`vue@${version}`);
    let files: StoreState["files"] = {};

    if (serializedState) {
      const saved = JSON.parse(atou(serializedState));
      for (const filename in saved) {
        files[filename] = new File(filename, saved[filename]);
      }
    } else {
      files = {
        [defaultMainFile]: new File(defaultMainFile, welcomeCode),
      };
    }
    // console.log("constructor", files);
    this.defaultVueRuntimeURL = defaultVueRuntimeURL;
    this.defaultVueServerRendererURL = defaultVueServerRendererURL;
    this.initialShowOutput = showOutput;
    this.initialOutputMode = outputMode as OutputModes;

    let mainFile = defaultMainFile;
    if (!files[mainFile]) {
      mainFile = Object.keys(files)[0];
    }
    this.state = reactive({
      mainFile,
      files,
      activeFile: files[mainFile],
      errors: [],
      vueRuntimeURL: this.defaultVueRuntimeURL,
      vueServerRendererURL: this.defaultVueServerRendererURL,
      resetFlip: true,
    });
    this.initImportMap();
  }

  // don't start compiling until the options are set
  init() {
    watchEffect(() => compileFile(this, this.state.activeFile));
    for (const file in this.state.files) {
      if (file !== defaultMainFile) {
        compileFile(this, this.state.files[file]);
      }
    }
  }

  setActive(filename: string) {
    this.state.activeFile = this.state.files[filename];
  }

  addFile(fileOrFilename: string | File): void {
    const file =
      typeof fileOrFilename === "string"
        ? new File(fileOrFilename)
        : fileOrFilename;
    this.state.files[file.filename] = file;
    if (!file.hidden) this.setActive(file.filename);
  }

  deleteFile(filename: string) {
    if (confirm(`Are you sure you want to delete ${filename}?`)) {
      if (this.state.activeFile.filename === filename) {
        this.state.activeFile = this.state.files[this.state.mainFile];
      }
      // delete this.state.files[filename];
      Vue.delete(this.state.files, filename);
    }
  }

  serialize() {
    return "#" + utoa(JSON.stringify(this.getFiles()));
  }

  getFiles() {
    const exported: Record<string, string> = {};
    for (const filename in this.state.files) {
      exported[filename] = this.state.files[filename].code;
    }
    return exported;
  }

  async setFiles(newFiles: Record<string, string>, mainFile = defaultMainFile) {
    const files: Record<string, File> = {};
    if (mainFile === defaultMainFile && !newFiles[mainFile]) {
      files[mainFile] = new File(mainFile, welcomeCode);
    }
    for (const filename in newFiles) {
      files[filename] = new File(filename, newFiles[filename]);
    }
    for (const file in files) {
      await compileFile(this, files[file]);
    }
    this.state.mainFile = mainFile;
    this.state.files = files;
    this.initImportMap();
    this.setActive(mainFile);
    this.forceSandboxReset();
  }

  private forceSandboxReset() {
    this.state.resetFlip = !this.state.resetFlip;
  }

  private initImportMap() {
    const map = this.state.files["import-map.json"];
    if (!map) {
      this.state.files["import-map.json"] = new File(
        "import-map.json",
        JSON.stringify(
          {
            imports: {
              vue: this.defaultVueRuntimeURL,
            },
          },
          null,
          2
        )
      );
    } else {
      try {
        const json = JSON.parse(map.code);
        if (!json.imports.vue) {
          json.imports.vue = this.defaultVueRuntimeURL;
          map.code = JSON.stringify(json, null, 2);
        }
        if (!json.imports["vue/server-renderer"]) {
          json.imports["vue/server-renderer"] =
            this.defaultVueServerRendererURL;
          map.code = JSON.stringify(json, null, 2);
        }
        // eslint-disable-next-line no-empty
      } catch (e) { }
    }
  }

  getImportMap() {
    try {
      return JSON.parse(this.state.files["import-map.json"].code);
    } catch (e) {
      this.state.errors = [
        `Syntax error in import-map.json: ${(e as Error).message}`,
      ];
      return {};
    }
  }

  setImportMap(map: {
    imports: Record<string, string>;
    scopes?: Record<string, Record<string, string>>;
  }) {
    this.state.files["import-map.json"]!.code = JSON.stringify(map, null, 2);
  }

  async setVueVersion(version: string) {
    this.vueVersion = version;
    const compilerUrl = `https://unpkg.com/@vue/compiler-sfc@${version}/dist/compiler-sfc.esm-browser.js`;
    const runtimeUrl = `https://unpkg.com/@vue/runtime-dom@${version}/dist/runtime-dom.esm-browser.js`;
    const ssrUrl = `https://unpkg.com/@vue/server-renderer@${version}/dist/server-renderer.esm-browser.js`;
    this.pendingCompiler = import(/* @vite-ignore */ compilerUrl);
    this.compiler = await this.pendingCompiler;
    this.pendingCompiler = null;
    this.state.vueRuntimeURL = runtimeUrl;
    this.state.vueServerRendererURL = ssrUrl;
    const importMap = this.getImportMap();
    const imports = importMap.imports || (importMap.imports = {});
    imports.vue = runtimeUrl;
    imports["vue/server-renderer"] = ssrUrl;
    this.setImportMap(importMap);
    this.forceSandboxReset();
    console.info(`[@vue/repl] Now using Vue version: ${version}`);
  }

  resetVueVersion() {
    this.vueVersion = undefined;
    this.compiler = defaultCompiler;
    this.state.vueRuntimeURL = this.defaultVueRuntimeURL;
    this.state.vueServerRendererURL = this.defaultVueServerRendererURL;
    const importMap = this.getImportMap();
    const imports = importMap.imports || (importMap.imports = {});
    imports.vue = this.defaultVueRuntimeURL;
    imports["vue/server-renderer"] = this.defaultVueServerRendererURL;
    this.setImportMap(importMap);
    this.forceSandboxReset();
    console.info(`[@vue/repl] Now using default Vue version`);
  }
}
